package cn.iocoder.yudao.framework.datapermission.core.rule.store;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.dto.StoreDataPermissionRespDTO;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@AllArgsConstructor
@Slf4j
public class StoreDataPermissionRule implements DataPermissionRule {

    /**
     * LoginUser 的 Context 缓存 Key
     */
    protected static final String CONTEXT_KEY = StoreDataPermissionRule.class.getSimpleName();

    private static final String STORE_COLUMN_NAME = "store_id";

    static final Expression EXPRESSION_NULL = new NullValue();

    private final PermissionApi permissionApi;

    /**
     * 基于店铺的表字段配置
     * 一般情况下，每个表的店铺编号字段是 store_id，通过该配置自定义。
     * <p>
     * key：表名
     * value：字段名
     */
    private final Map<String, String> storeColumns = new HashMap<>();

    /**
     * 所有表名，是 {@link #storeColumns}
     */
    private final Set<String> TABLE_NAMES = new HashSet<>();

    @Override
    public Set<String> getTableNames() {
        return TABLE_NAMES;
    }

    @Override
    public Expression getExpression(String tableName, Alias tableAlias) {
        // 只有有登陆用户的情况下，才进行数据权限的处理
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        if (loginUser == null) {
            return null;
        }
        // 只有管理员类型的用户，才进行数据权限的处理
        if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
            return null;
        }

        // 获得数据权限
        StoreDataPermissionRespDTO storeDataPermission = loginUser.getContext(CONTEXT_KEY, StoreDataPermissionRespDTO.class);
        // 从上下文中拿不到，则调用逻辑进行获取
        if (storeDataPermission == null) {
            storeDataPermission = permissionApi.getStoreDataPermission(loginUser.getId());
            if (storeDataPermission == null) {
                log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
                throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
                        loginUser.getId(), tableName, tableAlias.getName()));
            }
            // 添加到上下文中，避免重复计算
            loginUser.setContext(CONTEXT_KEY, storeDataPermission);
        }

        // 情况一，如果是 ALL 可查看全部，则无需拼接条件
        if (storeDataPermission.getAll()) {
            return null;
        }

        // 情况三，拼接 Store 和 User 的条件，最后组合
        Expression storeExpression = buildStoreExpression(tableName, tableAlias, storeDataPermission.getStoreIds());
        if (storeExpression == null) {
            // TODO 芋艿：获得不到条件的时候，暂时不抛出异常，而是不返回数据
            log.warn("[getExpression][LoginUser({}) Table({}/{}) StoreDataPermission({}) 构建的条件为空]",
                    JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(storeDataPermission));
//            throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
//                    loginUser.getId(), tableName, tableAlias.getName()));
            return EXPRESSION_NULL;
        }

        // 目前，如果有指定店铺 + 可查看自己，采用 OR 条件。即，WHERE (store_id IN ? OR user_id = ?)
        return new Parenthesis(storeExpression);
    }

    private Expression buildStoreExpression(String tableName, Alias tableAlias, Set<Long> storeIds) {
        // 如果不存在配置，则无需作为条件
        String columnName = storeColumns.get(tableName);
        if (StrUtil.isEmpty(columnName)) {
            return null;
        }
        // 如果为空，则无条件
        if (CollUtil.isEmpty(storeIds)) {
            return null;
        }
        // 拼接条件
        return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
                new ExpressionList(CollectionUtils.convertList(storeIds, LongValue::new)));
    }

    // ==================== 添加配置 ====================

    public void addStoreColumn(Class<? extends BaseDO> entityClass) {
        addStoreColumn(entityClass, STORE_COLUMN_NAME);
    }

    public void addStoreColumn(Class<? extends BaseDO> entityClass, String columnName) {
        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
        addStoreColumn(tableName, columnName);
    }

    public void addStoreColumn(String tableName, String columnName) {
        storeColumns.put(tableName, columnName);
        TABLE_NAMES.add(tableName);
    }

}
